knitr::opts_chunk$set(echo = TRUE)
library(config) # Utilisation d'un fichier de configuration, cf https://db.rstudio.com/best-practices/managing-credentials/#stored-in-a-file-with-config
library(tidyverse)
library(DBI) # pour les connexions aux BDD
library(RPostgreSQL) # driver postgres
library(RMariaDB) # driver MariaDB
library(RMySQL) # driver MySQL
library(lubridate) # calcul sur les dates
library(httr) #
library(jsonlite) # Gestion format json
library("readxl") # Lecture de fichiers xlsx
library(sf)
library(mapview)
library(plotly) # Graphiques interactifs
library(leaflet) # Cartes interactives
# Fonction pour traduire la réponse de l'API
api_reponse <- function(request) {
case_when(request$status_code==200 ~ "OK, tous les résultats sont présents dans la réponse",
request$status_code==206 ~ "OK, il reste des résultats",
request$status_code==400 ~ "Requête incorrecte",
TRUE ~ "Autre réponse")
}
# Fonction pour requêter l'API Hub'Eau
get_hubeau <- function(path, query) {
response <- GET(url = path, query = query) %>%
content(as = "text", encoding = "UTF-8") %>%
fromJSON(flatten = TRUE)
data = response$data # Le jeu de données est dans l'objet "data"
if(response$count > query$size) { # Si la taille de page est plus petite que le nb de résultats, faire une boucle pour les pages suivantes
pages <- ceiling(response$count/query$size)
# Affichage d'une barre de progression
pb <- winProgressBar(title = "Récupération des chroniques", min = 0,
max = pages, width = 300)
for(i in 2:pages){
query$page <- i
response_i <- GET(url = path, query = query) %>%
content(as = "text", encoding = "UTF-8") %>%
fromJSON(flatten = TRUE)
data <- rbind(data, response_i$data) # Concaténation des lignes récupérées
setWinProgressBar(pb, i, title=paste( round(i/pages*100, 0), "% chargé")) # Avancement de la barre de progression
} # Fin boucle for
close(pb)
} # Fin if
return(data)
} # Fin get_hubeau
# Insertion de données
db.insertion <- function(con,table,data) {
# Date de la dernière donnée en base
Date_max <- tbl(con, table) %>% summarise(Date_max = max(Date_de_la_mesure, na.rm = TRUE)) %>% pull(Date_max)
if(is.na(Date_max)) Date_max <- as.Date(params$date_debut)-1
# Données plus récentes à insérer
data <- data %>% filter(Date_de_la_mesure > Date_max)
# Existe-t-il des données à insérer ?
if (isTRUE(data %>% tally() > 0)) {
# Insertion des nouvelles lignes
dbWriteTable(con, table, data, overwrite=FALSE, append=TRUE,
fileEncoding="latin1")
# Horodate en commentaire
mise_a_jour <- paste0(format.Date(Sys.Date(),"%d/%m/%Y"), " : Actualisation ",params$actualisation)
dbGetQuery(con, paste0("ALTER TABLE ",table," COMMENT = '",mise_a_jour,"';"))
paste0("Données insérées : ",data%>%tally)
}
else {
paste0("Aucune donnée à insérer depuis le ", format.Date(Date_max,"%d/%m/%Y"))
}
} # fin db.insertion
# Base Mariadb OEB
con_eau_tbi <- dbConnect(RMariaDB::MariaDB(), default.file = '../../.my.cnf', groups="mysql_oeb",
dbname = "eau_tbi")
# Version localhost
con_oeb_tbi <- dbConnect(RMariaDB::MariaDB(), default.file = '../../.my.cnf', groups="mysql_local",
dbname = "oeb_tbi")
con_referentiels <- dbConnect(RMariaDB::MariaDB(), default.file = '../../.my.cnf', groups="mysql_oeb",
dbname = "eau_referentiels")
dbListTables(con_eau_tbi) # Lister les tables de la base
[1] "aelb_eeme_ce_import" "aelb_eeme_es_import" "aelb_eeme_mece_description"
[4] "aelb_eeme_mees_description" "oeb_eau_alteration_hydromorphologique" "oeb_eau_prelevement_eau_brute"
[7] "oeb_eau_prelevement_eau_brute_commune" "oeb_eau_prelevement_eau_brute_ouvrage" "oeb_eau_qualite_biologique_ce"
[10] "oeb_eau_qualite_biologique_ce_new" "oeb_eru" "oeb_eru_aglo"
[13] "oeb_eru_boue" "oeb_eru_steu" "oeb_import_donneepe3b1"
[16] "oeb_surface_culture_rpg2016" "oeb_tbi_irep_consoind" "oeb_tbi_nappe_moy"
[19] "oeb_tbi_nitrate_eau_souterraine" "oeb_tbi_nitrate_eau_souterraine_site" "oeb_tbi_nitrate_nappe"
[22] "oeb_tbi_niveaunappean" "oeb_tbi_onde" "oeb_tbi_peb"
[25] "oeb_tbi_prelevement_tmp" "oeb_tbi_qualitebio" "oeb_tbi_qualitebio_tmp"
[28] "oeb_tbi_qualitebio_tmp2" "oeb_tbi_sispea" "oeb_tbi_variationnappe_interannuelle"
[31] "oeb_tbi_variationnappe_intermensuelles" "oeb_tbi_variationnappe_mois" "oeb_tbi_variationnappe_pz"
[34] "oeb_tbi_variationnappe_tmp" "tbi_image_effectifcomplet" "tbi_image_exportcomplet"
conf <- config::get("postgres_dev")
con_postgresql_dev <- DBI::dbConnect(odbc::odbc(),
Driver = conf$driver,
servername = conf$server,
UID = conf$uid,
PWD = conf$pwd,
Port = conf$port,
database = 'eau',
encoding = "latin1")
# Récupération des infos sur les stations de mesure
# Liste des paramètres de la requête
query_sites = list(
code_departement= params$num_departement,
format='json',
size=params$pagination
)
# Requête des stations
hubeau_sites <- get_hubeau(path = params$api_sites, query = query_sites)
# Conversion au format géographique
sites <- st_as_sf(hubeau_sites, coords = c("coordonnee_x", "coordonnee_y"),
crs = 2154, agr = "constant")
# Récupération des résultats d'indices biologiques
# Liste des paramètres de la requête (lot 1 de départements)
query_resultats = list(
#code_departement = params$num_departement,
code_departement = "29,22,35",
code_indice = params$codes_parametres,
#les paramètres date_debut et date_fin créent un bug dans l'API https://github.com/BRGM/hubeau/issues/81
#date_debut_prelevement = params$date_debut,
#date_fin_prelevement = params$date_fin,
size = params$pagination,
format = 'json')
# Requête des chroniques
hubeau_resultats <- get_hubeau(path = params$api_resultats, query=query_resultats)
# Liste des paramètres de la requête (lot 2 de départements)
query_resultats = list(
#code_departement = params$num_departement,
code_departement = "56,50,44,49,53",
code_indice = params$codes_parametres,
#les paramètres date_debut et date_fin créent un bug dans l'API https://github.com/BRGM/hubeau/issues/81
#date_debut_prelevement = params$date_debut,
#date_fin_prelevement = params$date_fin,
size = params$pagination,
format = 'json')
# Requête des chroniques
hubeau_resultats <- get_hubeau(path = params$api_resultats, query=query_resultats)%>%union(hubeau_resultats)
Warning: call dbDisconnect() when finished working with a connection
Warning: call dbDisconnect() when finished working with a connection
Warning: call dbDisconnect() when finished working with a connection
# Conversion au format géographique
resultats <- st_as_sf(hubeau_resultats, coords = c("coordonnee_x", "coordonnee_y"),
crs = 2154, agr = "constant")
Import des référentiels géographiques
# couche des SAGEs bretons depuis Geobretagne
sages <- st_read("https://geobretagne.fr/geoserver/dreal_b/sage_dreal/wfs?SERVICE=WFS&REQUEST=GetCapabilities")
Reading layer `dreal_b:sage_dreal' from data source
`https://geobretagne.fr/geoserver/dreal_b/sage_dreal/wfs?SERVICE=WFS&REQUEST=GetCapabilities' using driver `WFS'
Simple feature collection with 21 features and 7 fields
Geometry type: MULTISURFACE
Dimension: XY
Bounding box: xmin: 123903.4 ymin: 6703536 xmax: 422117.4 ymax: 6882053
Projected CRS: RGF93 / Lambert-93
# pb pas moyen de manipuler cet objet => solution sur
# https://gis.stackexchange.com/questions/389814/r-st-centroid-geos-error-unknown-wkb-type-12/389854#389854
ensure_multipolygons <- function(X) {
tmp1 <- tempfile(fileext = ".gpkg")
tmp2 <- tempfile(fileext = ".gpkg")
st_write(X, tmp1)
gdalUtilities::ogr2ogr(tmp1, tmp2, f = "GPKG", nlt = "MULTIPOLYGON")
Y <- st_read(tmp2)
st_sf(st_drop_geometry(X), geom = st_geometry(Y))
}
sages <- ensure_multipolygons(sages)
Writing layer `file42dc70e711fa' to data source `C:\Users\tbesse\AppData\Local\Temp\RtmpIHxy8N\file42dc70e711fa.gpkg' using driver `GPKG'
Writing 21 features with 7 fields and geometry type Multi Surface.
Reading layer `file42dc70e711fa' from data source `C:\Users\tbesse\AppData\Local\Temp\RtmpIHxy8N\file42dc2c367dd1.gpkg' using driver `GPKG'
Simple feature collection with 21 features and 7 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: 123903.4 ymin: 6703536 xmax: 422117.4 ymax: 6882053
Projected CRS: RGF93 / Lambert-93
# couche des Hydroécorégions de niveau 2
her2 <- st_read("https://services.sandre.eaufrance.fr/geo/mdo?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&typename=Hydroecoregion2")%>%
st_transform(2154)
Reading layer `Hydroecoregion2' from data source
`https://services.sandre.eaufrance.fr/geo/mdo?SERVICE=WFS&VERSION=2.0.0&REQUEST=GetFeature&typename=Hydroecoregion2'
using driver `GML'
Simple feature collection with 114 features and 5 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: -4.79495 ymin: 41.36882 xmax: 9.560037 ymax: 51.08965
Geodetic CRS: WGS 84
Sélection des sites et des résultats sur les territoires des SAGE bretons
sites_sages <- sites %>%
st_join(sages) %>%
filter(!is.na(cd_sage))
resultats_sages <- resultats %>%
st_join(sages) %>%
filter(!is.na(cd_sage)) %>%
st_join(her2)
Les sites font-ils partie du réseau RCS ?
sites_rcs <- sites_sages %>%
# rowwise pour préciser que les opérations se font pour chaque ligne
rowwise()%>%
# Le code 0000000052 (RCS) est dans la liste des codes_réseaux
mutate(inclus_rcs = '0000000052' %in% unlist(codes_reseaux))%>%
select(code_station_hydrobio, inclus_rcs)
sites_sages %>%
ggplot()+
geom_sf(data=sages)+
geom_sf(aes(color=libelle_departement))

resultats_sages %>%
ggplot()+
geom_sf(data=sages)+
geom_sf(aes(color=libelle_qualification))

resultats_sages %>%
filter(code_indice == '5856')%>% #5856 IBD Indice Diatomées
ggplot()+
geom_sf(data=sages)+
geom_sf(aes(color=resultat_indice))

Jointure avec les tables référentiels
Sites inconnus
sites_inconnus <- sites_sages %>%
# tables des sites
left_join(tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","site")), by=c("code_station_hydrobio" = "code"), copy=TRUE, suffix = c("", ".site"))%>%
filter(is.na(site_id))
sites_inconnus %>%
distinct(code_station_hydrobio, libelle_station_hydrobio)
sites_inconnus %>%
ggplot()+
geom_sf(data=sages)+
geom_sf(aes(color=libelle_departement))

MAJ sites inconnus
insert_sites <- sites_inconnus %>%
mutate(coord_x = st_coordinates(st_transform(geometry, 2154))[,1],
coord_y = st_coordinates(st_transform(geometry, 2154))[,2],
longitude_wgs84 = st_coordinates(st_transform(geometry, 4326))[,1],
latitude_wgs84 = st_coordinates(st_transform(geometry, 4326))[,2],
typesite_id = '1',
projection_id = '2154',
source = 'OFB/NAIADES',
maj = format(Sys.Date(),"%Y-%m-%d")
)%>%
as_tibble() %>%
select(code = code_station_hydrobio,
libelle = libelle_station_hydrobio,
typesite_id,
coord_x,
coord_y,
projection_id,
longitude_wgs84,
latitude_wgs84,
source,
maj)
insert_sites
NA
Import de la table de correspondance site / EGA
correspondance_site_ega <- tbl(con_postgresql_dev, dbplyr::in_schema("eau_referentiel","geo_correspondance_site_ega"))%>%
mutate(typesite = 'SITE')
correspondance_site_ega
liste_parametres <- as.list(strsplit(params$codes_parametres, ",")[[1]])
parametres <- tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","parametre"))%>%
collect() %>%
filter(code %in% liste_parametres)
parametres
classes_qualite <- tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","join_parametre_classe"))%>%
left_join(tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","classe")), by="classe_id")%>%
collect()%>%
filter(parametre_id %in% parametres$parametre_id,
valide == 1)
classes_qualite
Table complète
table_indices <- resultats_sages %>%
mutate(CoordX_WGS84 = st_coordinates(st_transform(geometry, 4326))[,1],
CoordY_WGS84 = st_coordinates(st_transform(geometry, 4326))[,2],
date_prelevement = as.Date(date_prelevement)
)%>%
as_tibble()%>%
# tables des paramètres
left_join(parametres, by=c("code_indice"="code"), suffix = c("", ".parametre"))%>%
# Unité inconnue --> code sandre 0
# n (nombre) --> code sandre 214
# ‰ vs SMOW --> code sandre 32
mutate(unite_code = case_when(unite_indice == "Unité inconnue" ~ '0',
unite_indice == "n" ~ '214',
unite_indice == "‰ vs SMOW" ~ '32',
TRUE ~ unite_indice),
resultat_indice = ifelse(resultat_indice == 999, NA, resultat_indice)) %>%
# tables des unités
left_join(tbl(con_postgresql_dev, dbplyr::in_schema("eau_referentiel","unite")), by=c("unite_code" = "code"), copy=TRUE, suffix = c("", ".unite"))%>%
# tables des sites
left_join(tbl(con_postgresql_dev, dbplyr::in_schema("eau_structure","site")), by=c("code_station_hydrobio" = "code"), copy=TRUE, suffix = c("", ".site"))
table_indices
# Synthèse des données manquantes
table_indices %>%
summarise(
nb_lignes = n(),
lignes_sans_parametre = sum(is.na(parametre_id)),
lignes_sans_unite = sum(is.na(unite_id)),
lignes_sans_sites = sum(is.na(site_id))
)
# Unités inconnues de la table de référence
table_indices %>% filter(is.na(unite_id)) %>% distinct(unite_indice)
# Stations inconnues de la table des sites
table_indices %>% filter(is.na(site_id)) %>% distinct(libelle_station_hydrobio)
# Liste des codes indices
table_indices %>% distinct(code_indice)
NA
Exploration des données
table_indices_classes %>%
group_by(year(date_prelevement), libelle_support, libelle_indice) %>%
summarise(Nb_resultats = n(),
Annee = year(date_prelevement)) %>%
ggplot(aes(x = as.factor(Annee), y=Nb_resultats, fill=libelle_indice))+
geom_bar(stat="identity")+
facet_grid(libelle_support~.)
`summarise()` has grouped output by 'year(date_prelevement)', 'libelle_support', 'libelle_indice'. You can override using the `.groups` argument.

NA
table_indices_classes %>%
arrange(code)%>%
ggplot(aes(x = code, y=resultat_indice, fill = code))+
geom_boxplot()+
facet_wrap(~libelle_indice, scales = "free")

table_indices_classes
Import des données en base
Données au format de la table eau_structure.analyse_bio_esu - serveur PostgreSQL DEV
Mise en forme de la table
Sélection des nouvelles lignes à insérer
Insertion des nouvelles lignes
Données au format de la table eau_tbi.oeb_eau_qualite_biologique_ce - serveur MariaDB OEB
table_series_indices <- table_indices_classes_annee %>%
mutate(indice = paste(libelle, code_indice, sep=' - '))%>%
pivot_longer(cols= c("classe","resultat_indice","resultat_qualification"))%>%
mutate(Serie = case_when(name == 'classe' ~ paste('Classe',libelle_support,sep = ' - '),
name == 'resultat_indice' ~ paste('Indice',indice,sep = ' - '),
name == 'resultat_qualification' ~ paste('Qualification',indice,sep = ' - ')
)
)%>%
group_by(code_station_hydrobio,
longitude_wgs84,
latitude_wgs84,
libelle_support,
Serie,
symbole,
Annee)%>%
summarise(Resultat = max(value))%>%
ungroup()%>%
union(table_indices_classe_globale_annee)
`summarise()` has grouped output by 'code_station_hydrobio', 'longitude_wgs84', 'latitude_wgs84', 'libelle_support', 'Serie', 'symbole'. You can override using the `.groups` argument.
table_series_indices
Déclinaison par combinaison SITE / EGA
table_series_indices_ega <- table_series_indices%>%
# table des correspondances sites / UGA
left_join(correspondance_site_ega, by=c("code_station_hydrobio" = "cdsite"), copy = TRUE)%>%
left_join(sites_rcs, by="code_station_hydrobio")
table_series_indices_ega
Sélection des lignes nouvelles à insérer
insert_oeb_eau_qualite_biologique_ce <- oeb_eau_qualite_biologique_ce %>%
ungroup() %>%
# Retirer les lignes des résultats déjà existants dans la table
anti_join(tbl(con_eau_tbi,"oeb_eau_qualite_biologique_ce", by=c("Type_entitite_geographique", "Code_entitite_geographique", "Type_entitite_geographique_associee", "Code_entitite_geographique_associee", "Periode", "Serie")), copy = TRUE)%>%
# Retirer les stations hydro sans EGA identifiée
filter(!is.na(Type_entitite_geographique))%>%
# Retirer les lignes avec une valeur NA
drop_na()
Joining, by = c("Type_entitite_geographique", "Code_entitite_geographique", "Libelle_entitite_geographique", "CoordX_WGS84", "CoordY_WGS84", "Reseau_RCS", "Type_entitite_geographique_associee", "Code_entitite_geographique_associee", "Libelle_entitite_geographique_associee", "Periode", "Serie", "unite", "Resultat", "Source", "Mise_a_jour")
insert_oeb_eau_qualite_biologique_ce
Insertion des nouvelles lignes
Export pour le GIDE
# Depuis la base de données
#tbl(con_eau_tbi, "oeb_eau_qualite_biologique_ce_new")%>%
# ou depuis la table en mémoire
oeb_eau_qualite_biologique_ce %>%
write.table(file = paste0(params$path_dataviz,"\\GIDE\\","oeb_eau_qualite_biologique_ce.csv"), quote = TRUE, sep = ";",
eol = "\n", na = "", dec = ",",
fileEncoding = "UTF-8")
Export pour GEOB
oeb_eau_qualite_geob <- table_series_indices %>%
ungroup()%>%
left_join(select(sites_sages,code_station_hydrobio,libelle_station_hydrobio), by="code_station_hydrobio") %>%
mutate(Code_entitite_geographique = code_station_hydrobio,
Libelle_entitite_geographique = libelle_station_hydrobio,
CoordX_WGS84 = longitude_wgs84,
CoordY_WGS84 = latitude_wgs84,
Serie,
unite = symbole,
Type_entitite_geographique = 'SITE',
Source = 'OFB/NAIADES',
Mise_a_jour = format(Sys.Date(),"%Y-%m-%d")) %>%
select(Type_entitite_geographique,
Code_entitite_geographique,
Libelle_entitite_geographique,
CoordX_WGS84,
CoordY_WGS84,
Serie,
unite,
Source,
Mise_a_jour,
Annee,
Resultat) %>%
pivot_wider(values_from = Resultat, names_from = Annee, names_sort=TRUE)
oeb_eau_qualite_geob %>%
filter(Serie == 'Classe - Qualité biologique Globale') %>%
write.table(file = paste0(params$path_dataviz,"\\GEOB\\","oeb_eau_qualite_biologique_globale.csv"), quote = TRUE, sep = ";",
eol = "\n", na = "", dec = ",",
fileEncoding = "UTF-8")
oeb_eau_qualite_geob %>%
filter(Serie == 'Classe - Diatomées benthiques') %>%
write.table(file = paste0(params$path_dataviz,"\\GEOB\\","oeb_eau_qualite_diatomees.csv"), quote = TRUE, sep = ";",
eol = "\n", na = "", dec = ",",
fileEncoding = "UTF-8")
oeb_eau_qualite_geob %>%
filter(Serie == 'Classe - Macroinvertébrés aquatiques') %>%
write.table(file = paste0(params$path_dataviz,"\\GEOB\\","oeb_eau_qualite_macroinvertebres.csv"), quote = TRUE, sep = ";",
eol = "\n", na = "", dec = ",",
fileEncoding = "UTF-8")
oeb_eau_qualite_geob %>%
filter(Serie == 'Classe - Macrophytes') %>%
write.table(file = paste0(params$path_dataviz,"\\GEOB\\","oeb_eau_qualite_macrophytes.csv"), quote = TRUE, sep = ";",
eol = "\n", na = "", dec = ",",
fileEncoding = "UTF-8")
LS0tDQp0aXRsZTogIkludGVncmF0aW9uIGRlcyBkb25uw6llcyBoeWRyb2Jpb2xvZ2lxdWVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQpwYXJhbXM6DQogIGFwaV9zaXRlczogJ2h0dHBzOi8vaHViZWF1LmVhdWZyYW5jZS5mci9hcGkvdmJldGEvaHlkcm9iaW8vc3RhdGlvbnNfaHlkcm9iaW8/Jw0KICBhcGlfcmVzdWx0YXRzOiAnaHR0cHM6Ly9odWJlYXUuZWF1ZnJhbmNlLmZyL2FwaS92YmV0YS9oeWRyb2Jpby9pbmRpY2VzPycNCiAgbnVtX2RlcGFydGVtZW50OiAnMjIsMjksMzUsNTYsNTAsNDQsNDksNTMnICAjIDIyLDI5LDM1LDU2LDUwLDQ0LDQ5LDUzDQogIGNvZGVzX3BhcmFtZXRyZXM6ICc3MDM2LDU5MTAsMTAyMiw1ODU2LDI5MjgsNjk1OSw2OTUxLDY5NTUsMTAwMCwyNTI3JyAjIDcwMzYsNTkxMCwxMDIyLDU4NTYsMjkyOCw2OTU5LDY5NTEsNjk1NSwxMDAwLDI1MjcNCiAgZGF0ZV9kZWJ1dDogMjAxNy0wMS0wMQ0KICBkYXRlX2ZpbjogMjAxOS0xMi0zMQ0KICBwYWdpbmF0aW9uOiA1MDAwDQogIHBhdGhfZGF0YXZpejogJ086XDA0LkRBVEFWSVNVQUxJU0FUSU9OXElORElDQVRFVVJTX0JJT0xPR0lFXERDRV9FVEFUX0JJT0xPR0lRVUUnDQogIA0KLS0tDQoNCmBgYHtyIHNldHVwfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KbGlicmFyeShjb25maWcpICMgVXRpbGlzYXRpb24gZCd1biBmaWNoaWVyIGRlIGNvbmZpZ3VyYXRpb24sIGNmICBodHRwczovL2RiLnJzdHVkaW8uY29tL2Jlc3QtcHJhY3RpY2VzL21hbmFnaW5nLWNyZWRlbnRpYWxzLyNzdG9yZWQtaW4tYS1maWxlLXdpdGgtY29uZmlnDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoREJJKSAjIHBvdXIgbGVzIGNvbm5leGlvbnMgYXV4IEJERA0KbGlicmFyeShSUG9zdGdyZVNRTCkgIyBkcml2ZXIgcG9zdGdyZXMNCmxpYnJhcnkoUk1hcmlhREIpICMgZHJpdmVyIE1hcmlhREINCmxpYnJhcnkoUk15U1FMKSAjIGRyaXZlciBNeVNRTA0KbGlicmFyeShsdWJyaWRhdGUpICMgY2FsY3VsIHN1ciBsZXMgZGF0ZXMNCg0KbGlicmFyeShodHRyKSAjDQpsaWJyYXJ5KGpzb25saXRlKSAjIEdlc3Rpb24gZm9ybWF0IGpzb24NCg0KbGlicmFyeSgicmVhZHhsIikgIyBMZWN0dXJlIGRlIGZpY2hpZXJzIHhsc3gNCg0KbGlicmFyeShzZikNCmxpYnJhcnkobWFwdmlldykNCmxpYnJhcnkocGxvdGx5KSAjIEdyYXBoaXF1ZXMgaW50ZXJhY3RpZnMNCmxpYnJhcnkobGVhZmxldCkgIyBDYXJ0ZXMgaW50ZXJhY3RpdmVzDQoNCiMgRm9uY3Rpb24gcG91ciB0cmFkdWlyZSBsYSByw6lwb25zZSBkZSBsJ0FQSQ0KYXBpX3JlcG9uc2UgPC0gZnVuY3Rpb24ocmVxdWVzdCkgew0KICANCmNhc2Vfd2hlbihyZXF1ZXN0JHN0YXR1c19jb2RlPT0yMDAgfiAiT0ssIHRvdXMgbGVzIHLDqXN1bHRhdHMgc29udCBwcsOpc2VudHMgZGFucyBsYSByw6lwb25zZSIsDQogICAgICAgICAgcmVxdWVzdCRzdGF0dXNfY29kZT09MjA2IH4gIk9LLCBpbCByZXN0ZSBkZXMgcsOpc3VsdGF0cyIsDQogICAgICAgICAgcmVxdWVzdCRzdGF0dXNfY29kZT09NDAwIH4gIlJlcXXDqnRlIGluY29ycmVjdGUiLA0KICAgICAgICAgICAgVFJVRSB+ICJBdXRyZSByw6lwb25zZSIpDQp9DQoNCiMgRm9uY3Rpb24gcG91ciByZXF1w6p0ZXIgbCdBUEkgSHViJ0VhdQ0KDQpnZXRfaHViZWF1IDwtIGZ1bmN0aW9uKHBhdGgsIHF1ZXJ5KSB7DQogIA0KICByZXNwb25zZSA8LSBHRVQodXJsID0gcGF0aCwgcXVlcnkgPSBxdWVyeSkgJT4lIA0KICBjb250ZW50KGFzID0gInRleHQiLCBlbmNvZGluZyA9ICJVVEYtOCIpICU+JQ0KICBmcm9tSlNPTihmbGF0dGVuID0gVFJVRSkNCiAgDQpkYXRhID0gcmVzcG9uc2UkZGF0YSAjIExlIGpldSBkZSBkb25uw6llcyBlc3QgZGFucyBsJ29iamV0ICJkYXRhIg0KDQppZihyZXNwb25zZSRjb3VudCA+IHF1ZXJ5JHNpemUpIHsgIyBTaSBsYSB0YWlsbGUgZGUgcGFnZSBlc3QgcGx1cyBwZXRpdGUgcXVlIGxlIG5iIGRlIHLDqXN1bHRhdHMsIGZhaXJlIHVuZSBib3VjbGUgcG91ciBsZXMgcGFnZXMgc3VpdmFudGVzDQogIA0KcGFnZXMgPC0gY2VpbGluZyhyZXNwb25zZSRjb3VudC9xdWVyeSRzaXplKQ0KDQojIEFmZmljaGFnZSBkJ3VuZSBiYXJyZSBkZSBwcm9ncmVzc2lvbg0KcGIgPC0gd2luUHJvZ3Jlc3NCYXIodGl0bGUgPSAiUsOpY3Vww6lyYXRpb24gZGVzIGNocm9uaXF1ZXMiLCBtaW4gPSAwLA0KICAgICAgICAgICAgICAgICAgICAgbWF4ID0gcGFnZXMsIHdpZHRoID0gMzAwKQ0KDQpmb3IoaSBpbiAyOnBhZ2VzKXsNCiAgDQogIHF1ZXJ5JHBhZ2UgPC0gaQ0KICANCiAgcmVzcG9uc2VfaSA8LSBHRVQodXJsID0gcGF0aCwgcXVlcnkgPSBxdWVyeSkgJT4lIA0KICBjb250ZW50KGFzID0gInRleHQiLCBlbmNvZGluZyA9ICJVVEYtOCIpICU+JQ0KICBmcm9tSlNPTihmbGF0dGVuID0gVFJVRSkNCg0KZGF0YSA8LSByYmluZChkYXRhLCByZXNwb25zZV9pJGRhdGEpICMgQ29uY2F0w6luYXRpb24gZGVzIGxpZ25lcyByw6ljdXDDqXLDqWVzDQoNCnNldFdpblByb2dyZXNzQmFyKHBiLCBpLCB0aXRsZT1wYXN0ZSggcm91bmQoaS9wYWdlcyoxMDAsIDApLCAiJSBjaGFyZ8OpIikpICMgQXZhbmNlbWVudCBkZSBsYSBiYXJyZSBkZSBwcm9ncmVzc2lvbg0KDQp9ICMgRmluIGJvdWNsZSBmb3INCg0KY2xvc2UocGIpDQoNCn0gIyBGaW4gaWYNCg0KcmV0dXJuKGRhdGEpDQogIH0gIyBGaW4gZ2V0X2h1YmVhdQ0KDQojIEluc2VydGlvbiBkZSBkb25uw6llcw0KDQpkYi5pbnNlcnRpb24gPC0gZnVuY3Rpb24oY29uLHRhYmxlLGRhdGEpIHsNCiAgDQogICMgRGF0ZSBkZSBsYSBkZXJuacOocmUgZG9ubsOpZSBlbiBiYXNlDQogIERhdGVfbWF4IDwtIHRibChjb24sIHRhYmxlKSAlPiUgc3VtbWFyaXNlKERhdGVfbWF4ID0gbWF4KERhdGVfZGVfbGFfbWVzdXJlLCBuYS5ybSA9IFRSVUUpKSAlPiUgcHVsbChEYXRlX21heCkNCiAgaWYoaXMubmEoRGF0ZV9tYXgpKSBEYXRlX21heCA8LSBhcy5EYXRlKHBhcmFtcyRkYXRlX2RlYnV0KS0xDQogIA0KICAjIERvbm7DqWVzIHBsdXMgcsOpY2VudGVzIMOgIGluc8OpcmVyDQogIGRhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKERhdGVfZGVfbGFfbWVzdXJlID4gRGF0ZV9tYXgpDQogIA0KICAjIEV4aXN0ZS10LWlsIGRlcyBkb25uw6llcyDDoCBpbnPDqXJlciA/DQppZiAoaXNUUlVFKGRhdGEgJT4lIHRhbGx5KCkgPiAwKSkgew0KICANCiAgIyBJbnNlcnRpb24gZGVzIG5vdXZlbGxlcyBsaWduZXMNCiAgZGJXcml0ZVRhYmxlKGNvbiwgdGFibGUsIGRhdGEsIG92ZXJ3cml0ZT1GQUxTRSwgYXBwZW5kPVRSVUUsDQogICAgICAgICAgICAgZmlsZUVuY29kaW5nPSJsYXRpbjEiKQ0KICANCiAgIyBIb3JvZGF0ZSBlbiBjb21tZW50YWlyZQ0KbWlzZV9hX2pvdXIgPC0gcGFzdGUwKGZvcm1hdC5EYXRlKFN5cy5EYXRlKCksIiVkLyVtLyVZIiksICIgOiBBY3R1YWxpc2F0aW9uICIscGFyYW1zJGFjdHVhbGlzYXRpb24pDQoNCmRiR2V0UXVlcnkoY29uLCBwYXN0ZTAoIkFMVEVSIFRBQkxFICIsdGFibGUsIiBDT01NRU5UID0gJyIsbWlzZV9hX2pvdXIsIic7IikpDQoNCnBhc3RlMCgiRG9ubsOpZXMgaW5zw6lyw6llcyA6ICIsZGF0YSU+JXRhbGx5KQ0KDQp9DQogIGVsc2Ugew0KICAgIA0KICAgIHBhc3RlMCgiQXVjdW5lIGRvbm7DqWUgw6AgaW5zw6lyZXIgZGVwdWlzIGxlICIsIGZvcm1hdC5EYXRlKERhdGVfbWF4LCIlZC8lbS8lWSIpKQ0KICANCiAgICB9DQogIA0KfSAjIGZpbiBkYi5pbnNlcnRpb24NCmBgYA0KDQoNCmBgYHtyIGNvbm5leGlvbl9iZH0NCg0KIyBCYXNlIE1hcmlhZGIgT0VCDQpjb25fZWF1X3RiaSA8LSBkYkNvbm5lY3QoUk1hcmlhREI6Ok1hcmlhREIoKSwgZGVmYXVsdC5maWxlID0gJy4uLy4uLy5teS5jbmYnLCBncm91cHM9Im15c3FsX29lYiIsDQpkYm5hbWUgPSAiZWF1X3RiaSIpDQoNCiMgVmVyc2lvbiBsb2NhbGhvc3QNCmNvbl9vZWJfdGJpIDwtIGRiQ29ubmVjdChSTWFyaWFEQjo6TWFyaWFEQigpLCBkZWZhdWx0LmZpbGUgPSAnLi4vLi4vLm15LmNuZicsIGdyb3Vwcz0ibXlzcWxfbG9jYWwiLA0KZGJuYW1lID0gIm9lYl90YmkiKQ0KDQoNCmNvbl9yZWZlcmVudGllbHMgPC0gZGJDb25uZWN0KFJNYXJpYURCOjpNYXJpYURCKCksIGRlZmF1bHQuZmlsZSA9ICcuLi8uLi8ubXkuY25mJywgZ3JvdXBzPSJteXNxbF9vZWIiLA0KZGJuYW1lID0gImVhdV9yZWZlcmVudGllbHMiKQ0KDQoNCmRiTGlzdFRhYmxlcyhjb25fZWF1X3RiaSkgIyBMaXN0ZXIgbGVzIHRhYmxlcyBkZSBsYSBiYXNlDQoNCmNvbmYgPC0gY29uZmlnOjpnZXQoInBvc3RncmVzX2RldiIpDQoNCmNvbl9wb3N0Z3Jlc3FsX2RldiA8LSBEQkk6OmRiQ29ubmVjdChvZGJjOjpvZGJjKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIERyaXZlciAgICAgICA9IGNvbmYkZHJpdmVyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzZXJ2ZXJuYW1lICAgPSBjb25mJHNlcnZlciwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgVUlEID0gY29uZiR1aWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIFBXRCA9IGNvbmYkcHdkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBQb3J0ID0gY29uZiRwb3J0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhYmFzZSA9ICdlYXUnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbmNvZGluZyA9ICJsYXRpbjEiKQ0KYGBgDQoNCmBgYHtyIGh1YmVhdX0NCg0KIyBSw6ljdXDDqXJhdGlvbiBkZXMgaW5mb3Mgc3VyIGxlcyBzdGF0aW9ucyBkZSBtZXN1cmUNCg0KIyBMaXN0ZSBkZXMgcGFyYW3DqHRyZXMgZGUgbGEgcmVxdcOqdGUNCnF1ZXJ5X3NpdGVzID0gbGlzdCgNCiAgY29kZV9kZXBhcnRlbWVudD0gcGFyYW1zJG51bV9kZXBhcnRlbWVudCwNCiAgZm9ybWF0PSdqc29uJywNCiAgc2l6ZT1wYXJhbXMkcGFnaW5hdGlvbg0KICApDQoNCiMgUmVxdcOqdGUgZGVzIHN0YXRpb25zDQpodWJlYXVfc2l0ZXMgPC0gZ2V0X2h1YmVhdShwYXRoID0gcGFyYW1zJGFwaV9zaXRlcywgcXVlcnkgPSBxdWVyeV9zaXRlcykNCg0KIyBDb252ZXJzaW9uIGF1IGZvcm1hdCBnw6lvZ3JhcGhpcXVlDQpzaXRlcyA8LSBzdF9hc19zZihodWJlYXVfc2l0ZXMsIGNvb3JkcyA9IGMoImNvb3Jkb25uZWVfeCIsICJjb29yZG9ubmVlX3kiKSwgDQogICAgY3JzID0gMjE1NCwgYWdyID0gImNvbnN0YW50IikNCg0KIyBSw6ljdXDDqXJhdGlvbiBkZXMgcsOpc3VsdGF0cyBkJ2luZGljZXMgYmlvbG9naXF1ZXMNCg0KIyBMaXN0ZSBkZXMgcGFyYW3DqHRyZXMgZGUgbGEgcmVxdcOqdGUgKGxvdCAxIGRlIGTDqXBhcnRlbWVudHMpDQpxdWVyeV9yZXN1bHRhdHMgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAjY29kZV9kZXBhcnRlbWVudCA9IHBhcmFtcyRudW1fZGVwYXJ0ZW1lbnQsDQogICAgICAgICAgICAgICAgIGNvZGVfZGVwYXJ0ZW1lbnQgPSAiMjksMjIsMzUiLA0KICAgICAgICAgICAgICAgICBjb2RlX2luZGljZSA9IHBhcmFtcyRjb2Rlc19wYXJhbWV0cmVzLA0KICAgICAgICAgICAgICAgICAjbGVzIHBhcmFtw6h0cmVzIGRhdGVfZGVidXQgZXQgZGF0ZV9maW4gY3LDqWVudCB1biBidWcgZGFucyBsJ0FQSSBodHRwczovL2dpdGh1Yi5jb20vQlJHTS9odWJlYXUvaXNzdWVzLzgxDQogICAgICAgICAgICAgICAgICNkYXRlX2RlYnV0X3ByZWxldmVtZW50ID0gcGFyYW1zJGRhdGVfZGVidXQsDQogICAgICAgICAgICAgICAgICNkYXRlX2Zpbl9wcmVsZXZlbWVudCA9IHBhcmFtcyRkYXRlX2ZpbiwNCiAgICAgICAgICAgICAgICAgc2l6ZSA9IHBhcmFtcyRwYWdpbmF0aW9uLA0KICAgICAgICAgICAgICAgICBmb3JtYXQgPSAnanNvbicpDQoNCiMgUmVxdcOqdGUgZGVzIGNocm9uaXF1ZXMNCmh1YmVhdV9yZXN1bHRhdHMgPC0gZ2V0X2h1YmVhdShwYXRoID0gcGFyYW1zJGFwaV9yZXN1bHRhdHMsIHF1ZXJ5PXF1ZXJ5X3Jlc3VsdGF0cykNCg0KIyBMaXN0ZSBkZXMgcGFyYW3DqHRyZXMgZGUgbGEgcmVxdcOqdGUgKGxvdCAyIGRlIGTDqXBhcnRlbWVudHMpDQpxdWVyeV9yZXN1bHRhdHMgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAjY29kZV9kZXBhcnRlbWVudCA9IHBhcmFtcyRudW1fZGVwYXJ0ZW1lbnQsDQogICAgICAgICAgICAgICAgIGNvZGVfZGVwYXJ0ZW1lbnQgPSAiNTYsNTAsNDQsNDksNTMiLA0KICAgICAgICAgICAgICAgICBjb2RlX2luZGljZSA9IHBhcmFtcyRjb2Rlc19wYXJhbWV0cmVzLA0KICAgICAgICAgICAgICAgICAjbGVzIHBhcmFtw6h0cmVzIGRhdGVfZGVidXQgZXQgZGF0ZV9maW4gY3LDqWVudCB1biBidWcgZGFucyBsJ0FQSSBodHRwczovL2dpdGh1Yi5jb20vQlJHTS9odWJlYXUvaXNzdWVzLzgxDQogICAgICAgICAgICAgICAgICNkYXRlX2RlYnV0X3ByZWxldmVtZW50ID0gcGFyYW1zJGRhdGVfZGVidXQsDQogICAgICAgICAgICAgICAgICNkYXRlX2Zpbl9wcmVsZXZlbWVudCA9IHBhcmFtcyRkYXRlX2ZpbiwNCiAgICAgICAgICAgICAgICAgc2l6ZSA9IHBhcmFtcyRwYWdpbmF0aW9uLA0KICAgICAgICAgICAgICAgICBmb3JtYXQgPSAnanNvbicpDQoNCiMgUmVxdcOqdGUgZGVzIGNocm9uaXF1ZXMNCmh1YmVhdV9yZXN1bHRhdHMgPC0gZ2V0X2h1YmVhdShwYXRoID0gcGFyYW1zJGFwaV9yZXN1bHRhdHMsIHF1ZXJ5PXF1ZXJ5X3Jlc3VsdGF0cyklPiV1bmlvbihodWJlYXVfcmVzdWx0YXRzKQ0KDQojIENvbnZlcnNpb24gYXUgZm9ybWF0IGfDqW9ncmFwaGlxdWUNCnJlc3VsdGF0cyA8LSBzdF9hc19zZihodWJlYXVfcmVzdWx0YXRzLCBjb29yZHMgPSBjKCJjb29yZG9ubmVlX3giLCAiY29vcmRvbm5lZV95IiksIA0KICAgIGNycyA9IDIxNTQsIGFnciA9ICJjb25zdGFudCIpDQpgYGANCg0KIyBJbXBvcnQgZGVzIHLDqWbDqXJlbnRpZWxzIGfDqW9ncmFwaGlxdWVzDQoNCmBgYHtyIGNvdWNoZV9zYWdlc30NCiMgY291Y2hlIGRlcyBTQUdFcyBicmV0b25zIGRlcHVpcyBHZW9icmV0YWduZQ0Kc2FnZXMgPC0gc3RfcmVhZCgiaHR0cHM6Ly9nZW9icmV0YWduZS5mci9nZW9zZXJ2ZXIvZHJlYWxfYi9zYWdlX2RyZWFsL3dmcz9TRVJWSUNFPVdGUyZSRVFVRVNUPUdldENhcGFiaWxpdGllcyIpDQoNCiMgcGIgcGFzIG1veWVuIGRlIG1hbmlwdWxlciBjZXQgb2JqZXQgPT4gc29sdXRpb24gc3VyDQojIGh0dHBzOi8vZ2lzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy8zODk4MTQvci1zdC1jZW50cm9pZC1nZW9zLWVycm9yLXVua25vd24td2tiLXR5cGUtMTIvMzg5ODU0IzM4OTg1NA0KZW5zdXJlX211bHRpcG9seWdvbnMgPC0gZnVuY3Rpb24oWCkgew0KICB0bXAxIDwtIHRlbXBmaWxlKGZpbGVleHQgPSAiLmdwa2ciKQ0KICB0bXAyIDwtIHRlbXBmaWxlKGZpbGVleHQgPSAiLmdwa2ciKQ0KICBzdF93cml0ZShYLCB0bXAxKQ0KICBnZGFsVXRpbGl0aWVzOjpvZ3Iyb2dyKHRtcDEsIHRtcDIsIGYgPSAiR1BLRyIsIG5sdCA9ICJNVUxUSVBPTFlHT04iKQ0KICBZIDwtIHN0X3JlYWQodG1wMikNCiAgc3Rfc2Yoc3RfZHJvcF9nZW9tZXRyeShYKSwgZ2VvbSA9IHN0X2dlb21ldHJ5KFkpKQ0KfQ0KDQpzYWdlcyA8LSBlbnN1cmVfbXVsdGlwb2x5Z29ucyhzYWdlcykNCg0KIyBjb3VjaGUgZGVzIEh5ZHJvw6ljb3LDqWdpb25zIGRlIG5pdmVhdSAyDQoNCmhlcjIgPC0gc3RfcmVhZCgiaHR0cHM6Ly9zZXJ2aWNlcy5zYW5kcmUuZWF1ZnJhbmNlLmZyL2dlby9tZG8/U0VSVklDRT1XRlMmVkVSU0lPTj0yLjAuMCZSRVFVRVNUPUdldEZlYXR1cmUmdHlwZW5hbWU9SHlkcm9lY29yZWdpb24yIiklPiUNCiAgc3RfdHJhbnNmb3JtKDIxNTQpDQpgYGANCiMgU8OpbGVjdGlvbiBkZXMgc2l0ZXMgZXQgZGVzIHLDqXN1bHRhdHMgc3VyIGxlcyB0ZXJyaXRvaXJlcyBkZXMgU0FHRSBicmV0b25zDQoNCmBgYHtyIHNlbGVjdGlvbiBzdXIgbGVzIHRlcnJpdG9pcmVzIGRlcyBTQUdFc30NCg0Kc2l0ZXNfc2FnZXMgPC0gc2l0ZXMgJT4lIA0KICBzdF9qb2luKHNhZ2VzKSAlPiUgDQogIGZpbHRlcighaXMubmEoY2Rfc2FnZSkpDQoNCnJlc3VsdGF0c19zYWdlcyA8LSByZXN1bHRhdHMgJT4lIA0KICBzdF9qb2luKHNhZ2VzKSAlPiUgDQogIGZpbHRlcighaXMubmEoY2Rfc2FnZSkpICU+JSANCiAgc3Rfam9pbihoZXIyKQ0KDQpgYGANCg0KIyMgTGVzIHNpdGVzIGZvbnQtaWxzIHBhcnRpZSBkdSByw6lzZWF1IFJDUyA/DQoNCmBgYHtyIHNpdGVzX3Jjc30NCnNpdGVzX3JjcyA8LSBzaXRlc19zYWdlcyAlPiUNCiAgIyByb3d3aXNlIHBvdXIgcHLDqWNpc2VyIHF1ZSBsZXMgb3DDqXJhdGlvbnMgc2UgZm9udCBwb3VyIGNoYXF1ZSBsaWduZQ0KICByb3d3aXNlKCklPiUNCiAgIyBMZSBjb2RlIDAwMDAwMDAwNTIgKFJDUykgZXN0IGRhbnMgbGEgbGlzdGUgZGVzIGNvZGVzX3LDqXNlYXV4DQogIG11dGF0ZShpbmNsdXNfcmNzID0gJzAwMDAwMDAwNTInICVpbiUgdW5saXN0KGNvZGVzX3Jlc2VhdXgpKSU+JQ0KICBzZWxlY3QoY29kZV9zdGF0aW9uX2h5ZHJvYmlvLCBpbmNsdXNfcmNzKQ0KYGBgDQoNCmBgYHtyIGNhcnRlX3NpdGVzfQ0Kc2l0ZXNfc2FnZXMgJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGE9c2FnZXMpKw0KZ2VvbV9zZihhZXMoY29sb3I9bGliZWxsZV9kZXBhcnRlbWVudCkpDQpgYGANCg0KYGBge3IgY2FydGVfcmVzdWx0YXRzfQ0KcmVzdWx0YXRzX3NhZ2VzICU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhPXNhZ2VzKSsNCmdlb21fc2YoYWVzKGNvbG9yPWxpYmVsbGVfcXVhbGlmaWNhdGlvbikpDQpgYGANCg0KYGBge3IgY2FydGVfaW5kaWNlfQ0KcmVzdWx0YXRzX3NhZ2VzICU+JQ0KICBmaWx0ZXIoY29kZV9pbmRpY2UgPT0gJzU4NTYnKSU+JSAjNTg1NiBJQkQgSW5kaWNlIERpYXRvbcOpZXMNCiAgZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YT1zYWdlcykrDQpnZW9tX3NmKGFlcyhjb2xvcj1yZXN1bHRhdF9pbmRpY2UpKQ0KYGBgDQoNCiMgSm9pbnR1cmUgYXZlYyBsZXMgdGFibGVzIHLDqWbDqXJlbnRpZWxzDQoNCiMjIFNpdGVzIGluY29ubnVzDQoNCmBgYHtyIHNpdGVzX2luY29ubnVzfQ0Kc2l0ZXNfaW5jb25udXMgPC0gc2l0ZXNfc2FnZXMgJT4lDQogICMgdGFibGVzIGRlcyBzaXRlcw0KICBsZWZ0X2pvaW4odGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJzaXRlIikpLCBieT1jKCJjb2RlX3N0YXRpb25faHlkcm9iaW8iID0gImNvZGUiKSwgY29weT1UUlVFLCBzdWZmaXggPSBjKCIiLCAiLnNpdGUiKSklPiUNCiAgZmlsdGVyKGlzLm5hKHNpdGVfaWQpKQ0KDQpzaXRlc19pbmNvbm51cyAlPiUgDQogIGRpc3RpbmN0KGNvZGVfc3RhdGlvbl9oeWRyb2JpbywgbGliZWxsZV9zdGF0aW9uX2h5ZHJvYmlvKQ0KYGBgDQoNCmBgYHtyIGNhcnRlX3NpdGVzX2luY29ubnVzfQ0Kc2l0ZXNfaW5jb25udXMgJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGE9c2FnZXMpKw0KZ2VvbV9zZihhZXMoY29sb3I9bGliZWxsZV9kZXBhcnRlbWVudCkpDQpgYGANCg0KIyMgTUFKIHNpdGVzIGluY29ubnVzDQoNCmBgYHtyIHRhYmxlIHNpdGVzX2luY29ubnVzfQ0KDQppbnNlcnRfc2l0ZXMgPC0gc2l0ZXNfaW5jb25udXMgJT4lDQogIG11dGF0ZShjb29yZF94ID0gc3RfY29vcmRpbmF0ZXMoc3RfdHJhbnNmb3JtKGdlb21ldHJ5LCAyMTU0KSlbLDFdLA0KICAgICAgICAgY29vcmRfeSA9IHN0X2Nvb3JkaW5hdGVzKHN0X3RyYW5zZm9ybShnZW9tZXRyeSwgMjE1NCkpWywyXSwNCiAgICAgICAgIGxvbmdpdHVkZV93Z3M4NCA9IHN0X2Nvb3JkaW5hdGVzKHN0X3RyYW5zZm9ybShnZW9tZXRyeSwgNDMyNikpWywxXSwNCiAgICAgICAgIGxhdGl0dWRlX3dnczg0ID0gc3RfY29vcmRpbmF0ZXMoc3RfdHJhbnNmb3JtKGdlb21ldHJ5LCA0MzI2KSlbLDJdLA0KICAgICAgICAgdHlwZXNpdGVfaWQgPSAnMScsDQogICAgICAgICBwcm9qZWN0aW9uX2lkID0gJzIxNTQnLA0KICAgICAgICAgc291cmNlID0gJ09GQi9OQUlBREVTJywNCiAgICAgICAgIG1haiA9IGZvcm1hdChTeXMuRGF0ZSgpLCIlWS0lbS0lZCIpDQogICAgICAgICApJT4lIA0KICBhc190aWJibGUoKSAlPiUNCnNlbGVjdChjb2RlID0gY29kZV9zdGF0aW9uX2h5ZHJvYmlvLA0KbGliZWxsZSA9IGxpYmVsbGVfc3RhdGlvbl9oeWRyb2JpbywNCnR5cGVzaXRlX2lkLA0KY29vcmRfeCwNCmNvb3JkX3ksDQpwcm9qZWN0aW9uX2lkLA0KbG9uZ2l0dWRlX3dnczg0LA0KbGF0aXR1ZGVfd2dzODQsDQpzb3VyY2UsDQptYWopDQoNCmluc2VydF9zaXRlcw0KDQpgYGANCg0KYGBge3IgaW5zZXJ0IHNpdGVzX2luY29ubnVzLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KDQpzZjo6ZGJXcml0ZVRhYmxlKGNvbm4gPSBjb25fcG9zdGdyZXNxbF9kZXYsIG5hbWUgPSBJZChzY2hlbWEgPSAiZWF1X3N0cnVjdHVyZSIsdGFibGUgPSAic2l0ZSIpLCB2YWx1ZSA9IGluc2VydF9zaXRlcywgb3ZlcndyaXRlPUZBTFNFLCBhcHBlbmQ9VFJVRSwgZmlsZUVuY29kaW5nPSJsYXRpbjEiKQ0KYGBgDQoNCiMjIEltcG9ydCBkZSBsYSB0YWJsZSBkZSBjb3JyZXNwb25kYW5jZSBzaXRlIC8gRUdBDQoNCmBgYHtyIGltcG9ydF9lYXVfY29ycmVzcG9uZGFuY2Vfc2l0ZV9lZ2F9DQpjb3JyZXNwb25kYW5jZV9zaXRlX2VnYSA8LSB0YmwoY29uX3Bvc3RncmVzcWxfZGV2LCBkYnBseXI6OmluX3NjaGVtYSgiZWF1X3JlZmVyZW50aWVsIiwiZ2VvX2NvcnJlc3BvbmRhbmNlX3NpdGVfZWdhIikpJT4lDQogIG11dGF0ZSh0eXBlc2l0ZSA9ICdTSVRFJykNCg0KY29ycmVzcG9uZGFuY2Vfc2l0ZV9lZ2ENCmBgYA0KDQpgYGB7ciBpbXBvcnQgcGFyYW1ldHJlc30NCg0KbGlzdGVfcGFyYW1ldHJlcyA8LSBhcy5saXN0KHN0cnNwbGl0KHBhcmFtcyRjb2Rlc19wYXJhbWV0cmVzLCAiLCIpW1sxXV0pDQoNCnBhcmFtZXRyZXMgPC0gdGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJwYXJhbWV0cmUiKSklPiUNCiAgY29sbGVjdCgpICU+JQ0KICBmaWx0ZXIoY29kZSAlaW4lIGxpc3RlX3BhcmFtZXRyZXMpDQoNCnBhcmFtZXRyZXMNCmBgYA0KDQpgYGB7ciBpbXBvcnQgc2V1aWxzIGRlIHF1YWxpdMOpfQ0KDQpjbGFzc2VzX3F1YWxpdGUgPC0gdGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJqb2luX3BhcmFtZXRyZV9jbGFzc2UiKSklPiUNCiAgbGVmdF9qb2luKHRibChjb25fcG9zdGdyZXNxbF9kZXYsIGRicGx5cjo6aW5fc2NoZW1hKCJlYXVfc3RydWN0dXJlIiwiY2xhc3NlIikpLCBieT0iY2xhc3NlX2lkIiklPiUNCiAgY29sbGVjdCgpJT4lDQogIGZpbHRlcihwYXJhbWV0cmVfaWQgJWluJSBwYXJhbWV0cmVzJHBhcmFtZXRyZV9pZCwNCiAgICAgICAgIHZhbGlkZSA9PSAxKQ0KDQpjbGFzc2VzX3F1YWxpdGUNCmBgYA0KDQojIyBUYWJsZSBjb21wbMOodGUNCg0KYGBge3IgdGFibGUgaW5kaWNlc30NCnRhYmxlX2luZGljZXMgPC0gcmVzdWx0YXRzX3NhZ2VzICU+JQ0KICBtdXRhdGUoQ29vcmRYX1dHUzg0ID0gc3RfY29vcmRpbmF0ZXMoc3RfdHJhbnNmb3JtKGdlb21ldHJ5LCA0MzI2KSlbLDFdLA0KICAgICAgICAgQ29vcmRZX1dHUzg0ID0gc3RfY29vcmRpbmF0ZXMoc3RfdHJhbnNmb3JtKGdlb21ldHJ5LCA0MzI2KSlbLDJdLA0KICAgICAgICAgZGF0ZV9wcmVsZXZlbWVudCA9IGFzLkRhdGUoZGF0ZV9wcmVsZXZlbWVudCkNCiAgICAgICAgICklPiUNCiAgYXNfdGliYmxlKCklPiUNCiAgIyB0YWJsZXMgZGVzIHBhcmFtw6h0cmVzDQogIGxlZnRfam9pbihwYXJhbWV0cmVzLCBieT1jKCJjb2RlX2luZGljZSI9ImNvZGUiKSwgc3VmZml4ID0gYygiIiwgIi5wYXJhbWV0cmUiKSklPiUNCiAgIyBVbml0w6kgaW5jb25udWUgLS0+IGNvZGUgc2FuZHJlIDANCiAgIyBuIChub21icmUpIC0tPiBjb2RlIHNhbmRyZSAyMTQNCiAgIyDigLAgdnMgU01PVyAtLT4gY29kZSBzYW5kcmUgMzINCiAgbXV0YXRlKHVuaXRlX2NvZGUgPSBjYXNlX3doZW4odW5pdGVfaW5kaWNlID09ICJVbml0w6kgaW5jb25udWUiIH4gJzAnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVuaXRlX2luZGljZSA9PSAibiIgfiAnMjE0JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bml0ZV9pbmRpY2UgPT0gIuKAsCB2cyBTTU9XIiB+ICczMicsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHVuaXRlX2luZGljZSksDQogICAgICAgICByZXN1bHRhdF9pbmRpY2UgPSBpZmVsc2UocmVzdWx0YXRfaW5kaWNlID09IDk5OSwgTkEsIHJlc3VsdGF0X2luZGljZSkpICU+JQ0KICAjIHRhYmxlcyBkZXMgdW5pdMOpcw0KICBsZWZ0X2pvaW4odGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9yZWZlcmVudGllbCIsInVuaXRlIikpLCBieT1jKCJ1bml0ZV9jb2RlIiA9ICJjb2RlIiksIGNvcHk9VFJVRSwgc3VmZml4ID0gYygiIiwgIi51bml0ZSIpKSU+JQ0KICAjIHRhYmxlcyBkZXMgc2l0ZXMNCiAgbGVmdF9qb2luKHRibChjb25fcG9zdGdyZXNxbF9kZXYsIGRicGx5cjo6aW5fc2NoZW1hKCJlYXVfc3RydWN0dXJlIiwic2l0ZSIpKSwgYnk9YygiY29kZV9zdGF0aW9uX2h5ZHJvYmlvIiA9ICJjb2RlIiksIGNvcHk9VFJVRSwgc3VmZml4ID0gYygiIiwgIi5zaXRlIikpDQoNCnRhYmxlX2luZGljZXMgDQpgYGANCg0KYGBge3IgZG9ubmVlc19tYW5xdWFudGVzfQ0KIyBTeW50aMOoc2UgZGVzIGRvbm7DqWVzIG1hbnF1YW50ZXMNCnRhYmxlX2luZGljZXMgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBuYl9saWduZXMgPSBuKCksDQogICAgbGlnbmVzX3NhbnNfcGFyYW1ldHJlID0gc3VtKGlzLm5hKHBhcmFtZXRyZV9pZCkpLA0KICAgIGxpZ25lc19zYW5zX3VuaXRlID0gc3VtKGlzLm5hKHVuaXRlX2lkKSksDQogICAgbGlnbmVzX3NhbnNfc2l0ZXMgPSBzdW0oaXMubmEoc2l0ZV9pZCkpDQogICkNCg0KIyBVbml0w6lzIGluY29ubnVlcyBkZSBsYSB0YWJsZSBkZSByw6lmw6lyZW5jZQ0KdGFibGVfaW5kaWNlcyAlPiUgZmlsdGVyKGlzLm5hKHVuaXRlX2lkKSkgJT4lIGRpc3RpbmN0KHVuaXRlX2luZGljZSkNCg0KIyBTdGF0aW9ucyBpbmNvbm51ZXMgZGUgbGEgdGFibGUgZGVzIHNpdGVzDQp0YWJsZV9pbmRpY2VzICU+JSBmaWx0ZXIoaXMubmEoc2l0ZV9pZCkpICU+JSBkaXN0aW5jdChsaWJlbGxlX3N0YXRpb25faHlkcm9iaW8pDQoNCiMgTGlzdGUgZGVzIGNvZGVzIGluZGljZXMNCnRhYmxlX2luZGljZXMgJT4lIGRpc3RpbmN0KGNvZGVfaW5kaWNlKQ0KDQpgYGANCg0KDQojIyBDbGFzc2VzIGRlcyBpbmRpY2VzDQoNCmBgYHtyIGNsYXNzZV9pbmRpY2VzfQ0KdGFibGVfaW5kaWNlc19jbGFzc2VzIDwtIHRhYmxlX2luZGljZXMgJT4lIA0KICBsZWZ0X2pvaW4oY2xhc3Nlc19xdWFsaXRlLCBieSA9IGMoInBhcmFtZXRyZV9pZCIpLCBjb3B5ID0gVFJVRSwgc3VmZml4PWMoIiIsIi5jbGFzc2UiKSkgJT4lDQogIGZpbHRlcihyZXN1bHRhdF9pbmRpY2UgPCBib3JuZV9zdXBfZXhjbHVlICYgcmVzdWx0YXRfaW5kaWNlID49IGJvcm5lX2luZl9pbmNsdWUpDQoNCnRhYmxlX2luZGljZXNfY2xhc3Nlcw0KYGBgDQoNCiMgRXhwbG9yYXRpb24gZGVzIGRvbm7DqWVzDQoNCmBgYHtyIGdyYXBoZV9pbmRpY2VzLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xNn0NCnRhYmxlX2luZGljZXNfY2xhc3NlcyAlPiUNCiAgZ3JvdXBfYnkoeWVhcihkYXRlX3ByZWxldmVtZW50KSwgbGliZWxsZV9zdXBwb3J0LCBsaWJlbGxlX2luZGljZSkgJT4lDQogIHN1bW1hcmlzZShOYl9yZXN1bHRhdHMgPSBuKCksDQogICAgICAgICAgICBBbm5lZSA9IHllYXIoZGF0ZV9wcmVsZXZlbWVudCkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoQW5uZWUpLCB5PU5iX3Jlc3VsdGF0cywgZmlsbD1saWJlbGxlX2luZGljZSkpKw0KICAgICAgICAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpKw0KICBmYWNldF9ncmlkKGxpYmVsbGVfc3VwcG9ydH4uKQ0KICANCmBgYA0KDQpgYGB7ciBncmFwaGVfaW5kaWNlc19jbGFzc2VzLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xNn0NCnRhYmxlX2luZGljZXNfY2xhc3NlcyAlPiUNCiAgYXJyYW5nZShjb2RlKSU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBjb2RlLCB5PXJlc3VsdGF0X2luZGljZSwgZmlsbCA9IGNvZGUpKSsNCiAgICAgICAgICAgZ2VvbV9ib3hwbG90KCkrDQogIGZhY2V0X3dyYXAofmxpYmVsbGVfaW5kaWNlLCBzY2FsZXMgPSAiZnJlZSIpDQogIA0KdGFibGVfaW5kaWNlc19jbGFzc2VzDQpgYGANCg0KDQojIFRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcw0KDQojIyBEb25uw6llcyBhbm51ZWxsZXMNCg0KYGBge3IgdGFibGVfaW5kaWNlc19jbGFzc2VzX2FubmVlfQ0KDQp0YWJsZV9pbmRpY2VzX2NsYXNzZXNfYW5uZWUgPC0gdGFibGVfaW5kaWNlc19jbGFzc2VzICU+JQ0KICBtdXRhdGUoQW5uZWUgPSB5ZWFyKGFzLkRhdGUoZGF0ZV9wcmVsZXZlbWVudCkpKSAlPiUNCiAgZ3JvdXBfYnkoY29kZV9zdGF0aW9uX2h5ZHJvYmlvLA0KICAgICAgICAgICBsb25naXR1ZGVfd2dzODQsDQogICAgICAgICAgIGxhdGl0dWRlX3dnczg0LA0KICAgICAgICAgICBsaWJlbGxlX3N1cHBvcnQsDQogICAgICAgICAgIGNvZGVfaW5kaWNlLA0KICAgICAgICAgICBwYXJhbWV0cmVfaWQsDQogICAgICAgICAgIGxpYmVsbGUsDQogICAgICAgICAgIHN5bWJvbGUsDQogICAgICAgICAgIEFubmVlKSAlPiUNCiAgc3VtbWFyaXNlKGNsYXNzZSA9IGFzLmludGVnZXIobWF4KGNvZGUpKSwgDQogICAgICAgICAgICByZXN1bHRhdF9pbmRpY2UgPSBtZWFuKHJlc3VsdGF0X2luZGljZSksDQogICAgICAgICAgICByZXN1bHRhdF9xdWFsaWZpY2F0aW9uID0gYXMuaW50ZWdlcihtYXgoY29kZV9xdWFsaWZpY2F0aW9uKSkpDQoNCnRhYmxlX2luZGljZXNfY2xhc3Nlc19hbm5lZQ0KYGBgDQoNCiMjIENsYXNzZSBkZSBxdWFsaXTDqSBiaW9sb2dpcXVlIGdsb2JhbGUgKG1heCBkZXMgY2xhc3NlcyBwYXIgaW5kaWNlKQ0KDQpgYGB7cn0NCnRhYmxlX2luZGljZXNfY2xhc3NlX2dsb2JhbGVfYW5uZWUgPC0gdGFibGVfaW5kaWNlc19jbGFzc2VzX2FubmVlICU+JQ0KICBncm91cF9ieShjb2RlX3N0YXRpb25faHlkcm9iaW8sIGxvbmdpdHVkZV93Z3M4NCwgbGF0aXR1ZGVfd2dzODQsIEFubmVlKSU+JQ0KICBzdW1tYXJpc2UoUmVzdWx0YXQgPSBtYXgoY2xhc3NlKSklPiUNCiAgbXV0YXRlKFNlcmllID0gJ0NsYXNzZSAtIFF1YWxpdMOpIGJpb2xvZ2lxdWUgR2xvYmFsZScsIA0KICAgICAgICAgbGliZWxsZV9zdXBwb3J0ID0gJ1F1YWxpdMOpIGJpb2xvZ2lxdWUgR2xvYmFsZScsDQogICAgICAgICBzeW1ib2xlID0gJ1gnKQ0KDQp0YWJsZV9pbmRpY2VzX2NsYXNzZV9nbG9iYWxlX2FubmVlDQpgYGANCg0KIyBJbXBvcnQgZGVzIGRvbm7DqWVzIGVuIGJhc2UNCg0KIyMgRG9ubsOpZXMgYXUgZm9ybWF0IGRlIGxhIHRhYmxlIGVhdV9zdHJ1Y3R1cmUuYW5hbHlzZV9iaW9fZXN1IC0gc2VydmV1ciBQb3N0Z3JlU1FMIERFVg0KDQojIyMgTWlzZSBlbiBmb3JtZSBkZSBsYSB0YWJsZQ0KDQpgYGB7ciBhbmFseXNlX2Jpb19lc3UsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQoNCmFuYWx5c2VfYmlvX2VzdSA8LSB0YWJsZV9pbmRpY2VzICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHJlc3VsdGF0X2luZGljZSkpJT4lDQogIGxlZnRfam9pbihzZWxlY3QodGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJkYXRlIikpLCBkYXRlX2lkLCBkYXRlX2R1X2pvdXIpLCBieT1jKCJkYXRlX3ByZWxldmVtZW50IiA9ICJkYXRlX2R1X2pvdXIiKSwgY29weT1UUlVFKSU+JQ0KICBsZWZ0X2pvaW4oc2VsZWN0KHRibChjb25fcG9zdGdyZXNxbF9kZXYsIGRicGx5cjo6aW5fc2NoZW1hKCJlYXVfc3RydWN0dXJlIiwic3VwcG9ydCIpKSwgc3VwcG9ydF9pZCwgY29kZSksIGJ5PWMoImNvZGVfc3VwcG9ydCIgPSAiY29kZSIpLCBjb3B5PVRSVUUpJT4lDQogIGxlZnRfam9pbihzZWxlY3QodGJsKGNvbl9wb3N0Z3Jlc3FsX2RldiwgZGJwbHlyOjppbl9zY2hlbWEoImVhdV9zdHJ1Y3R1cmUiLCJyZW1hcnF1ZSIpKSwgcmVtYXJxdWVfaWQsIGNvZGUpLCBieT1jKCJjb2RlX3F1YWxpZmljYXRpb24iID0gImNvZGUiKSwgY29weT1UUlVFKSU+JQ0KICBtdXRhdGUocHJlbGV2ZW1lbnRfY29kZSA9IHBhc3RlMChjb2RlX3N0YXRpb25faHlkcm9iaW8sZGF0ZV9pZCksDQogICAgICAgICByZGRfaWQgPSAwLA0KICAgICAgICAgbWlsaWV1X2lkID0gMywNCiAgICAgICAgIGZyYWN0aW9uX2lkID0gMjIsDQogICAgICAgICBsaW1pdGVfcXVhbnRpZmljYXRpb24gPSAwLA0KICAgICAgICAgc291cmNlID0gJ09GQi9OQUlBREVTJywNCiAgICAgICAgIG1haiA9IGZvcm1hdChTeXMuRGF0ZSgpLCIlWS0lbS0lZCIpKSU+JQ0KICBzZWxlY3Qoc2l0ZV9pZCwNCiAgICAgICAgIGRhdGVfaWQsDQogICAgICAgICByZGRfaWQsDQogICAgICAgICBwcmVsZXZlbWVudF9jb2RlLA0KICAgICAgICAgbWlsaWV1X2lkLA0KICAgICAgICAgc3VwcG9ydF9pZCwNCiAgICAgICAgIGZyYWN0aW9uX2lkLA0KICAgICAgICAgcGFyYW1ldHJlX2lkLA0KICAgICAgICAgcmVzdWx0YXQgPSByZXN1bHRhdF9pbmRpY2UsDQogICAgICAgICByZW1hcnF1ZV9pZCwNCiAgICAgICAgIGxpbWl0ZV9xdWFudGlmaWNhdGlvbiwNCiAgICAgICAgIHNvdXJjZSwNCiAgICAgICAgIG1haikNCg0KYW5hbHlzZV9iaW9fZXN1DQpgYGANCg0KIyMjIFPDqWxlY3Rpb24gZGVzIG5vdXZlbGxlcyBsaWduZXMgw6AgaW5zw6lyZXINCg0KYGBge3IgaW5zZXJ0X2FuYWx5c2VfYmlvX2VzdX0NCg0KaW5zZXJ0X2FuYWx5c2VfYmlvX2VzdSA8LSBhbmFseXNlX2Jpb19lc3UNCg0KYGBgDQoNCiMjIyBJbnNlcnRpb24gZGVzIG5vdXZlbGxlcyBsaWduZXMNCg0KYGBge3IgaW5zZXJ0IHJlc3VsdGF0c19pbmNvbm51cywgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmRiRXhlY3V0ZShjb25fcG9zdGdyZXNxbF9kZXYsICJUUlVOQ0FURSBUQUJMRSBlYXVfc3RydWN0dXJlLmFuYWx5c2VfYmlvX2VzdSIpDQoNCmRiQXBwZW5kVGFibGUoY29ubiA9IGNvbl9wb3N0Z3Jlc3FsX2RldiwgbmFtZSA9IElkKHNjaGVtYSA9ICJlYXVfc3RydWN0dXJlIix0YWJsZSA9ICJhbmFseXNlX2Jpb19lc3UiKSwgdmFsdWUgPSBpbnNlcnRfYW5hbHlzZV9iaW9fZXN1LCBmaWxlRW5jb2Rpbmc9ImxhdGluMSIpDQpgYGANCg0KIyMgRG9ubsOpZXMgYXUgZm9ybWF0IGRlIGxhIHRhYmxlIGVhdV90Ymkub2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2UgLSBzZXJ2ZXVyIE1hcmlhREIgT0VCDQoNCmBgYHtyIHNlcmllc19pbmRpY2VzX2FubnVlbHN9DQoNCnRhYmxlX3Nlcmllc19pbmRpY2VzIDwtIHRhYmxlX2luZGljZXNfY2xhc3Nlc19hbm5lZSAlPiUgDQogIG11dGF0ZShpbmRpY2UgPSBwYXN0ZShsaWJlbGxlLCBjb2RlX2luZGljZSwgc2VwPScgLSAnKSklPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHM9IGMoImNsYXNzZSIsInJlc3VsdGF0X2luZGljZSIsInJlc3VsdGF0X3F1YWxpZmljYXRpb24iKSklPiUNCiAgbXV0YXRlKFNlcmllID0gY2FzZV93aGVuKG5hbWUgPT0gJ2NsYXNzZScgfiBwYXN0ZSgnQ2xhc3NlJyxsaWJlbGxlX3N1cHBvcnQsc2VwID0gJyAtICcpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9PSAncmVzdWx0YXRfaW5kaWNlJyB+IHBhc3RlKCdJbmRpY2UnLGluZGljZSxzZXAgPSAnIC0gJyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID09ICdyZXN1bHRhdF9xdWFsaWZpY2F0aW9uJyB+IHBhc3RlKCdRdWFsaWZpY2F0aW9uJyxpbmRpY2Usc2VwID0gJyAtICcpDQogICAgICAgICAgICAgICAgICAgICAgICAgICApDQogICAgICAgICApJT4lDQogIGdyb3VwX2J5KGNvZGVfc3RhdGlvbl9oeWRyb2JpbywNCiAgICAgICAgICAgbG9uZ2l0dWRlX3dnczg0LA0KICAgICAgICAgICBsYXRpdHVkZV93Z3M4NCwNCiAgICAgICAgICAgbGliZWxsZV9zdXBwb3J0LA0KICAgICAgICAgICBTZXJpZSwNCiAgICAgICAgICAgc3ltYm9sZSwNCiAgICAgICAgICAgQW5uZWUpJT4lDQogIHN1bW1hcmlzZShSZXN1bHRhdCA9IG1heCh2YWx1ZSkpJT4lDQogIHVuZ3JvdXAoKSU+JQ0KICB1bmlvbih0YWJsZV9pbmRpY2VzX2NsYXNzZV9nbG9iYWxlX2FubmVlKQ0KDQp0YWJsZV9zZXJpZXNfaW5kaWNlcw0KYGBgDQojIyMgRMOpY2xpbmFpc29uIHBhciBjb21iaW5haXNvbiBTSVRFIC8gRUdBDQoNCmBgYHtyIHRhYmxlX3Nlcmllc19pbmRpY2VzX2VnYX0NCnRhYmxlX3Nlcmllc19pbmRpY2VzX2VnYSA8LSB0YWJsZV9zZXJpZXNfaW5kaWNlcyU+JQ0KICAjIHRhYmxlIGRlcyBjb3JyZXNwb25kYW5jZXMgc2l0ZXMgLyBVR0ENCiAgbGVmdF9qb2luKGNvcnJlc3BvbmRhbmNlX3NpdGVfZWdhLCBieT1jKCJjb2RlX3N0YXRpb25faHlkcm9iaW8iID0gImNkc2l0ZSIpLCBjb3B5ID0gVFJVRSklPiUNCiAgbGVmdF9qb2luKHNpdGVzX3JjcywgYnk9ImNvZGVfc3RhdGlvbl9oeWRyb2JpbyIpDQoNCnRhYmxlX3Nlcmllc19pbmRpY2VzX2VnYQ0KYGBgDQojIyMgTWlzZSBlbiBmb3JtZSBkZSBsYSB0YWJsZQ0KDQpgYGB7ciB0YWJsZSBvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZX0NCg0Kb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2UgPC0gdGFibGVfc2VyaWVzX2luZGljZXNfZWdhICU+JQ0KICBtdXRhdGUoUGVyaW9kZSA9IGFzLmNoYXJhY3RlcihBbm5lZSksDQogICAgICAgICBTb3VyY2UgPSAnT0ZCL05BSUFERVMnLA0KICAgICAgICAgTWlzZV9hX2pvdXIgPSBmb3JtYXQoU3lzLkRhdGUoKSwiJVktJW0tJWQiKQ0KICAgICAgICAgKSU+JSANCiAgc2VsZWN0KFR5cGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlID0gdHlwZXNpdGUsDQogICAgICAgICBDb2RlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSA9IGNvZGVfc3RhdGlvbl9oeWRyb2JpbywNCiAgICAgICAgIExpYmVsbGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlID0gbGJzaXRlLA0KICAgICAgICAgQ29vcmRYX1dHUzg0ID0gbG9uZ2l0dWRlX3dnczg0LA0KICAgICAgICAgQ29vcmRZX1dHUzg0ID0gbGF0aXR1ZGVfd2dzODQsDQogICAgICAgICBSZXNlYXVfUkNTID0gaW5jbHVzX3JjcywNCiAgICAgICAgIFR5cGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlX2Fzc29jaWVlID0gdHlwZWVnYSwNCiAgICAgICAgIENvZGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlX2Fzc29jaWVlID0gY2RlZ2EsDQogICAgICAgICBMaWJlbGxlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZV9hc3NvY2llZSA9IGxiZWdhLA0KICAgICAgICAgUGVyaW9kZSwNCiAgICAgICAgIFNlcmllLA0KICAgICAgICAgdW5pdGUgPSBzeW1ib2xlLA0KICAgICAgICAgUmVzdWx0YXQsDQogICAgICAgICBTb3VyY2UsDQogICAgICAgICBNaXNlX2Ffam91cg0KICApDQoNCm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlDQoNCmBgYA0KIyMjIFPDqWxlY3Rpb24gZGVzIGxpZ25lcyBub3V2ZWxsZXMgw6AgaW5zw6lyZXINCg0KDQpgYGB7ciB0YWJsZSBpbnNlcnRfb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2V9DQoNCmluc2VydF9vZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZSA8LSBvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUNCiAgIyBSZXRpcmVyIGxlcyBsaWduZXMgZGVzIHLDqXN1bHRhdHMgZMOpasOgIGV4aXN0YW50cyBkYW5zIGxhIHRhYmxlDQogIGFudGlfam9pbih0YmwoY29uX2VhdV90YmksIm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlIiwgYnk9YygiVHlwZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWUiLCAiQ29kZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWUiLCAiVHlwZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWVfYXNzb2NpZWUiLCAiQ29kZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWVfYXNzb2NpZWUiLCAiUGVyaW9kZSIsICJTZXJpZSIpKSwgY29weSA9IFRSVUUpJT4lDQogICMgUmV0aXJlciBsZXMgc3RhdGlvbnMgaHlkcm8gc2FucyBFR0EgaWRlbnRpZmnDqWUNCiAgZmlsdGVyKCFpcy5uYShUeXBlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSkpJT4lDQogICMgUmV0aXJlciBsZXMgbGlnbmVzIGF2ZWMgdW5lIHZhbGV1ciBOQQ0KICBkcm9wX25hKCkNCg0KaW5zZXJ0X29lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlDQpgYGANCg0KIyMjIEluc2VydGlvbiBkZXMgbm91dmVsbGVzIGxpZ25lcw0KDQpgYGB7ciBpbnNlcnQgb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2UsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQpkYkV4ZWN1dGUoY29uX2VhdV90YmksICJUUlVOQ0FURSBUQUJMRSBvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZSIpDQoNCmRiQXBwZW5kVGFibGUoY29ubiA9IGNvbl9lYXVfdGJpLCBuYW1lID0gIm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlIiwgdmFsdWUgPSBpbnNlcnRfb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2UsIGZpbGVFbmNvZGluZz0ibGF0aW4xIikNCmBgYA0KDQojIyMgRXhwb3J0IHBvdXIgbGUgR0lERQ0KDQpgYGB7ciBleHBvcnQgb2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2V9DQojIERlcHVpcyBsYSBiYXNlIGRlIGRvbm7DqWVzDQojdGJsKGNvbl9lYXVfdGJpLCAib2ViX2VhdV9xdWFsaXRlX2Jpb2xvZ2lxdWVfY2VfbmV3IiklPiUNCiMgb3UgZGVwdWlzIGxhIHRhYmxlIGVuIG3DqW1vaXJlDQpvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9jZSAlPiUgIA0Kd3JpdGUudGFibGUoZmlsZSA9IHBhc3RlMChwYXJhbXMkcGF0aF9kYXRhdml6LCJcXEdJREVcXCIsIm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2NlLmNzdiIpLCBxdW90ZSA9IFRSVUUsIHNlcCA9ICI7IiwNCiAgICAgICAgICAgIGVvbCA9ICJcbiIsIG5hID0gIiIsIGRlYyA9ICIsIiwNCiAgICAgICAgICAgIGZpbGVFbmNvZGluZyA9ICJVVEYtOCIpDQpgYGANCg0KIyMjIEV4cG9ydCBwb3VyIEdFT0INCg0KYGBge3Igb2ViX2VhdV9xdWFsaXRlX2dlb2J9DQpvZWJfZWF1X3F1YWxpdGVfZ2VvYiA8LSB0YWJsZV9zZXJpZXNfaW5kaWNlcyAgJT4lDQogIHVuZ3JvdXAoKSU+JQ0KICBsZWZ0X2pvaW4oc2VsZWN0KHNpdGVzX3NhZ2VzLGNvZGVfc3RhdGlvbl9oeWRyb2JpbyxsaWJlbGxlX3N0YXRpb25faHlkcm9iaW8pLCBieT0iY29kZV9zdGF0aW9uX2h5ZHJvYmlvIikgICU+JQ0KICBtdXRhdGUoQ29kZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWUgPSBjb2RlX3N0YXRpb25faHlkcm9iaW8sDQogICAgICAgICBMaWJlbGxlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSA9IGxpYmVsbGVfc3RhdGlvbl9oeWRyb2JpbywNCiAgICAgICAgIENvb3JkWF9XR1M4NCA9IGxvbmdpdHVkZV93Z3M4NCwNCiAgICAgICAgIENvb3JkWV9XR1M4NCA9IGxhdGl0dWRlX3dnczg0LA0KICAgICAgICAgU2VyaWUsDQogICAgICAgICB1bml0ZSA9IHN5bWJvbGUsDQogICAgICAgICBUeXBlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSA9ICdTSVRFJywNCiAgICAgICAgIFNvdXJjZSA9ICdPRkIvTkFJQURFUycsDQogICAgICAgICBNaXNlX2Ffam91ciA9IGZvcm1hdChTeXMuRGF0ZSgpLCIlWS0lbS0lZCIpKSAlPiUNCiAgc2VsZWN0KFR5cGVfZW50aXRpdGVfZ2VvZ3JhcGhpcXVlLA0KICAgICAgICAgQ29kZV9lbnRpdGl0ZV9nZW9ncmFwaGlxdWUsDQogICAgICAgICBMaWJlbGxlX2VudGl0aXRlX2dlb2dyYXBoaXF1ZSwNCiAgICAgICAgIENvb3JkWF9XR1M4NCwNCiAgICAgICAgIENvb3JkWV9XR1M4NCwNCiAgICAgICAgIFNlcmllLA0KICAgICAgICAgdW5pdGUsDQogICAgICAgICBTb3VyY2UsDQogICAgICAgICBNaXNlX2Ffam91ciwNCiAgICAgICAgIEFubmVlLA0KICAgICAgICAgUmVzdWx0YXQpICU+JQ0KICBwaXZvdF93aWRlcih2YWx1ZXNfZnJvbSA9IFJlc3VsdGF0LCBuYW1lc19mcm9tID0gQW5uZWUsIG5hbWVzX3NvcnQ9VFJVRSkNCmBgYA0KDQoNCmBgYHtyIGV4cG9ydCBvZWJfZWF1X3F1YWxpdGVfYmlvbG9naXF1ZV9nbG9iYWxlfQ0KDQpvZWJfZWF1X3F1YWxpdGVfZ2VvYiAgJT4lDQogIGZpbHRlcihTZXJpZSA9PSAnQ2xhc3NlIC0gUXVhbGl0w6kgYmlvbG9naXF1ZSBHbG9iYWxlJykgICU+JQ0Kd3JpdGUudGFibGUoZmlsZSA9IHBhc3RlMChwYXJhbXMkcGF0aF9kYXRhdml6LCJcXEdFT0JcXCIsIm9lYl9lYXVfcXVhbGl0ZV9iaW9sb2dpcXVlX2dsb2JhbGUuY3N2IiksIHF1b3RlID0gVFJVRSwgc2VwID0gIjsiLA0KICAgICAgICAgICAgZW9sID0gIlxuIiwgbmEgPSAiIiwgZGVjID0gIiwiLA0KICAgICAgICAgICAgZmlsZUVuY29kaW5nID0gIlVURi04IikNCmBgYA0KDQpgYGB7ciBleHBvcnQgb2ViX2VhdV9xdWFsaXRlX2RpYXRvbWVlc30NCg0Kb2ViX2VhdV9xdWFsaXRlX2dlb2IgJT4lDQogIGZpbHRlcihTZXJpZSA9PSAnQ2xhc3NlIC0gRGlhdG9tw6llcyBiZW50aGlxdWVzJykgJT4lDQp3cml0ZS50YWJsZShmaWxlID0gcGFzdGUwKHBhcmFtcyRwYXRoX2RhdGF2aXosIlxcR0VPQlxcIiwib2ViX2VhdV9xdWFsaXRlX2RpYXRvbWVlcy5jc3YiKSwgcXVvdGUgPSBUUlVFLCBzZXAgPSAiOyIsDQogICAgICAgICAgICBlb2wgPSAiXG4iLCBuYSA9ICIiLCBkZWMgPSAiLCIsDQogICAgICAgICAgICBmaWxlRW5jb2RpbmcgPSAiVVRGLTgiKQ0KYGBgDQoNCmBgYHtyIGV4cG9ydCBvZWJfZWF1X3F1YWxpdGVfbWFjcm9pbnZlcnRlYnJlc30NCg0Kb2ViX2VhdV9xdWFsaXRlX2dlb2IgJT4lDQogIGZpbHRlcihTZXJpZSA9PSAnQ2xhc3NlIC0gTWFjcm9pbnZlcnTDqWJyw6lzIGFxdWF0aXF1ZXMnKSAlPiUNCndyaXRlLnRhYmxlKGZpbGUgPSBwYXN0ZTAocGFyYW1zJHBhdGhfZGF0YXZpeiwiXFxHRU9CXFwiLCJvZWJfZWF1X3F1YWxpdGVfbWFjcm9pbnZlcnRlYnJlcy5jc3YiKSwgcXVvdGUgPSBUUlVFLCBzZXAgPSAiOyIsDQogICAgICAgICAgICBlb2wgPSAiXG4iLCBuYSA9ICIiLCBkZWMgPSAiLCIsDQogICAgICAgICAgICBmaWxlRW5jb2RpbmcgPSAiVVRGLTgiKQ0KYGBgDQoNCmBgYHtyIGV4cG9ydCBvZWJfZWF1X3F1YWxpdGVfbWFjcm9waHl0ZXN9DQoNCm9lYl9lYXVfcXVhbGl0ZV9nZW9iICU+JQ0KICBmaWx0ZXIoU2VyaWUgPT0gJ0NsYXNzZSAtIE1hY3JvcGh5dGVzJykgJT4lDQp3cml0ZS50YWJsZShmaWxlID0gcGFzdGUwKHBhcmFtcyRwYXRoX2RhdGF2aXosIlxcR0VPQlxcIiwib2ViX2VhdV9xdWFsaXRlX21hY3JvcGh5dGVzLmNzdiIpLCBxdW90ZSA9IFRSVUUsIHNlcCA9ICI7IiwNCiAgICAgICAgICAgIGVvbCA9ICJcbiIsIG5hID0gIiIsIGRlYyA9ICIsIiwNCiAgICAgICAgICAgIGZpbGVFbmNvZGluZyA9ICJVVEYtOCIpDQpgYGANCg==